﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.Core.Common.Models;
using Orchard.Core.Contents;
using Orchard.Core.Routable.Models;
using Orchard.Core.Routable.Services;
using Orchard.Core.XmlRpc;
using Orchard.Core.XmlRpc.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Mvc.Extensions;
using Orchard.Security;

namespace Contrib.RemotePages {
    public class XmlRpcHandler : IXmlRpcHandler {
        private const string PageContentType = "page";
        private readonly IContentManager _contentManager;
        private readonly IAuthorizationService _authorizationService;
        private readonly IMembershipService _membershipService;
        private readonly RouteCollection _routeCollection;
        private readonly IRoutableService _routableService;

        public XmlRpcHandler(IContentManager contentManager, IAuthorizationService authorizationService,
            IMembershipService membershipService, RouteCollection routeCollection, IRoutableService routableService) {
            _contentManager = contentManager;
            _authorizationService = authorizationService;
            _membershipService = membershipService;
            _routeCollection = routeCollection;
            _routableService = routableService;
            Logger = NullLogger.Instance;
            T = NullLocalizer.Instance;
        }

        public ILogger Logger { get; set; }
        public Localizer T { get; set; }

        public void SetCapabilities(XElement options) {
            const string manifestUri = "http://schemas.microsoft.com/wlw/manifest/weblog";
            options.SetElementValue(XName.Get("supportsPages", manifestUri), "Yes");
            options.SetElementValue(XName.Get("supportsSlug", manifestUri), "Yes");
        }

        public void Process(XmlRpcContext context) {
            var urlHelper = new UrlHelper(context.ControllerContext.RequestContext, _routeCollection);

            switch (context.Request.MethodName) {
                case "wp.getPages":
                    var result = MetaWeblogGetPages(urlHelper,
                                                    Convert.ToString(context.Request.Params[1].Value),
                                                    Convert.ToString(context.Request.Params[2].Value));
                    context.Response = new XRpcMethodResponse().Add(result);
                    break;
                case "wp.getPageList":
                    context.Response = new XRpcMethodResponse()
                        .Add(MetaWeblogGetPageList(
                            Convert.ToString(context.Request.Params[1].Value),
                            Convert.ToString(context.Request.Params[2].Value)));
                    break;
                case "wp.getPage":
                    context.Response = new XRpcMethodResponse()
                        .Add(MetaWeblogGetPage(
                            urlHelper,
                            Convert.ToInt32(context.Request.Params[1].Value),
                            Convert.ToString(context.Request.Params[2].Value),
                            Convert.ToString(context.Request.Params[3].Value)));
                    break;
                case "wp.newPage":
                    context.Response = new XRpcMethodResponse()
                        .Add(MetaWeblogNewPage(
                            Convert.ToString(context.Request.Params[1].Value),
                            Convert.ToString(context.Request.Params[2].Value),
                            (XRpcStruct) context.Request.Params[3].Value,
                            Convert.ToBoolean(context.Request.Params[4].Value),
                            context._drivers));
                    break;
                case "wp.editPage":
                    context.Response = new XRpcMethodResponse()
                        .Add(MetaWeblogEditPage(
                            Convert.ToInt32(context.Request.Params[1].Value),
                            Convert.ToString(context.Request.Params[2].Value),
                            Convert.ToString(context.Request.Params[3].Value),
                            (XRpcStruct) context.Request.Params[4].Value,
                            Convert.ToBoolean(context.Request.Params[5].Value),
                            context._drivers));
                    break;
                case "wp.deletePage":
                    context.Response = new XRpcMethodResponse()
                        .Add(MetaWeblogDeletePage(
                            Convert.ToString(context.Request.Params[1].Value),
                            Convert.ToString(context.Request.Params[2].Value),
                            Convert.ToInt32(context.Request.Params[3].Value),
                            context._drivers));
                    break;
            }
        }

        private XRpcArray MetaWeblogGetPages(
            UrlHelper urlHelper,
            string userName,
            string password) {
            var pages = new XRpcArray();
            var user = ValidateUser(userName, password);

            foreach (var page in _contentManager.Query(VersionOptions.Latest, new[] {PageContentType}).List().Where(page => _authorizationService.TryCheckAccess(Permissions.EditContent, user, page))) {
                pages.Add(BuildPageStruct(urlHelper, page));
            }

            return pages;
        }

        private XRpcArray MetaWeblogGetPageList(
            string userName,
            string password) {
            var pages = new XRpcArray();
            var user = ValidateUser(userName, password);

            foreach (var page in _contentManager.Query(new[] {PageContentType}).List().Where(page => _authorizationService.TryCheckAccess(Permissions.EditContent, user, page))) {
                pages.Add(BuildSimplePageStruct(page));
            }

            return pages;
        }

        private XRpcStruct MetaWeblogGetPage(
            UrlHelper urlHelper,
            int pageId,
            string userName,
            string password) {
            var user = ValidateUser(userName, password);
            var page = _contentManager.Get(pageId, VersionOptions.Latest);

            return _authorizationService.TryCheckAccess(Permissions.EditContent, user, page)
                ? BuildPageStruct(urlHelper, page)
                : null;
        }

        private int MetaWeblogNewPage(
            string userName,
            string password,
            XRpcStruct content,
            bool publish,
            IEnumerable<IXmlRpcDriver> drivers) {

            IUser user = ValidateUser(userName, password);

            _authorizationService.CheckAccess(publish ? Permissions.PublishContent : Permissions.EditContent, user, null);

            var page = _contentManager.New(PageContentType);
            if (page == null)
                throw new ArgumentException();

            var title = content.Optional<string>("title");
            var description = content.Optional<string>("description");
            var slug = content.Optional<string>("wp_slug");

            // BodyPart
            if (page.Is<BodyPart>()) {
                page.As<BodyPart>().Text = description;
            }

            //CommonPart
            if (page.Is<ICommonPart>()) {
                page.As<ICommonPart>().Owner = user;
            }

            //RoutePart
            if (page.Is<RoutePart>()) {
                page.As<RoutePart>().Title = title;
                page.As<RoutePart>().Slug = slug;
                _routableService.FillSlugFromTitle(page.As<RoutePart>());
                page.As<RoutePart>().Path = page.As<RoutePart>().GetPathWithSlug(page.As<RoutePart>().Slug);
            }

            _contentManager.Create(page, VersionOptions.Draft);

            var publishedUtc = content.Optional<DateTime?>("dateCreated");
            if (publish && (publishedUtc == null || publishedUtc <= DateTime.UtcNow))
                _contentManager.Publish(page);

            foreach (var driver in drivers)
                driver.Process(page.Id);

            return page.Id;
        }

        private bool MetaWeblogEditPage(
            int pageId,
            string userName,
            string password,
            XRpcStruct content,
            bool publish,
            IEnumerable<IXmlRpcDriver> drivers) {

            var user = ValidateUser(userName, password);
            var page = _contentManager.Get(pageId, VersionOptions.DraftRequired);
            if (page == null)
                throw new ArgumentException();

            _authorizationService.CheckAccess(publish ? Permissions.PublishContent : Permissions.EditContent, user, page);

            var title = content.Optional<string>("title");
            var description = content.Optional<string>("description");
            var slug = content.Optional<string>("wp_slug");

            // BodyPart
            if (page.Is<BodyPart>()) {
                page.As<BodyPart>().Text = description;
            }

            //RoutePart
            if (page.Is<RoutePart>()) {
                page.As<RoutePart>().Title = title;
                page.As<RoutePart>().Slug = slug;
                _routableService.FillSlugFromTitle(page.As<RoutePart>());
                page.As<RoutePart>().Path = page.As<RoutePart>().GetPathWithSlug(page.As<RoutePart>().Slug);
            }

            var publishedUtc = content.Optional<DateTime?>("dateCreated");
            if (publish && (publishedUtc == null || publishedUtc <= DateTime.UtcNow))
                _contentManager.Publish(page);

            foreach (var driver in drivers)
                driver.Process(page.Id);

            return true;
        }

        private bool MetaWeblogDeletePage(
            string userName,
            string password,
            int pageId,
            IEnumerable<IXmlRpcDriver> drivers) {

            var user = ValidateUser(userName, password);
            var page = _contentManager.Get(pageId, VersionOptions.Latest);
            if (page == null)
                throw new ArgumentException();

            _authorizationService.CheckAccess(Permissions.DeleteContent, user, page);

            foreach (var driver in drivers)
                driver.Process(page.Id);

            _contentManager.Remove(page);
            return true;
        }

        private XRpcStruct BuildPageStruct(UrlHelper urlHelper, IContent page) {
            var pageRoute = page.As<RoutePart>();
            var pageBody = page.As<BodyPart>();
            var url = urlHelper.AbsoluteAction(() => urlHelper.Action(null, _contentManager.GetItemMetadata(page).DisplayRouteValues));
            var pageStruct = new XRpcStruct()
                .Set("page_id", page.Id)
                .Set("title", pageRoute != null ? pageRoute.Title : T("unknown").Text)
                .Set("wp_slug", pageRoute != null ? pageRoute.Slug : "")
                .Set("description", pageBody != null ? pageBody.Text : "")
                .Set("link", url)
                .Set("permaLink", url);

            var pageCommon = page.As<CommonPart>();
            if (pageCommon != null && pageCommon.PublishedUtc.HasValue) {
                pageStruct.Set("date_created_gmt", pageCommon.PublishedUtc.Value.AddDays(-1));
            }

            return pageStruct;
        }

        private XRpcStruct BuildSimplePageStruct(IContent page) {
            var pageRoute = page.As<RoutePart>();
            var pageStruct = new XRpcStruct()
                .Set("page_id", page.Id)
                .Set("title", pageRoute != null ? pageRoute.Title : T("unknown").Text);

            var pageCommon = page.As<CommonPart>();
            if (pageCommon != null) {
                pageStruct.Set("dateCreated", pageCommon.PublishedUtc);
            }

            return pageStruct;
        }

        private IUser ValidateUser(string userName, string password) {
            var user = _membershipService.ValidateUser(userName, password);
            if (user == null) {
                throw new OrchardCoreException(T("The username or e-mail or password provided is incorrect."));
            }

            return user;
        }
   }
}